/*
 * InputMediaSource.cpp
 *
 *  Created on: 2018年6月24日
 *      Author: zhengboyuan
 */

#include "InputMediaSource.h"
#include "MFormatUtil.h"
#include "FfmpegUtil.h"
#include "CodecIDConvert.h"
#include "CLog.h"
#include "MediaProp.h"


namespace av
{

static const int MAX_DELAY_TIME = 1000 * 4;
static const int MIN_DELAY_TIME = 1000 * 1;


InputMediaSource::InputMediaSource():
	m_delayTime(),
	m_textPacketCount(),
	m_parser(),
	m_pContext(),
	m_videoPoint(),
	m_dumpEnabled()
{
}

InputMediaSource::~InputMediaSource()
{
}

int InputMediaSource::open(const MFormat& fmt)
{
	MFormatUtil::copyTo(&fmt, m_originFormat);

	if (m_originFormat.m_codec != av::MEDIA_CODEC_PS)
	{
		m_format = m_originFormat;
		openParser(m_format.m_codec);
	}

	if (m_dumpEnabled)
	{
		m_fileSink.open("dump.pkt");
		m_fileSink.onMediaFormat(m_originFormat);
	}
	
    return 0;
}

bool InputMediaSource::write(MPacket& pkt)
{
	if (m_dumpEnabled)
	{
		m_fileSink.writePacket(pkt.type, pkt.data, pkt.size, pkt.pts, pkt.duration, pkt.flags);
	}

	if (m_originFormat.m_codec == av::MEDIA_CODEC_PS)
	{
		if (pkt.type == av::MEDIA_TYPE_SUBTITLE)
		{
			return writePacket(pkt);
		}
		else
		{
			return writePs(pkt);
		}
	}
	else if (pkt.type == MEDIA_TYPE_VIDEO)
	{
		return parseWrite(pkt);
	}

	return writePacket(pkt);
}

void InputMediaSource::flush()
{
    m_pktQueue.clear();

	resetTextPacketCount();
}


int InputMediaSource::open(const std::string& url, const std::string& params)
{
    return 0;
}

void InputMediaSource::close()
{
    m_format.clear();
    m_pktQueue.clear();

	resetTextPacketCount();

	m_fileSink.close();
}

bool InputMediaSource::isOpen()
{
    return m_format.isValid();
}

bool InputMediaSource::getFormat(MediaFormat& fmt)
{
	comn::AutoCritSec lock(m_cs);
    fmt = m_format;
    return m_format.isValid();
}

int InputMediaSource::getDuration()
{
    return m_format.m_duration;
}

int InputMediaSource::play()
{
    return 0;
}

int InputMediaSource::pause()
{
    return 0;
}

void InputMediaSource::stop()
{
    //
}

int InputMediaSource::getState()
{
    return 0;
}

bool InputMediaSource::seekable()
{
    return false;
}

int InputMediaSource::seek(int64_t offset)
{
    return 0;
}

int64_t InputMediaSource::getTime()
{
    return 0;
}

int InputMediaSource::setScale(float scale)
{
    return 0;
}

float InputMediaSource::getScale()
{
    return 1.0f;
}

int InputMediaSource::read(AVPacket& pkt)
{
	if (m_delayTime > 0)
	{
		return readPacket(pkt, 20);
	}
	else
	{
		return readPacket(pkt, 20);
	}
}

void InputMediaSource::interrupt()
{
    m_pktQueue.cancelWait();
}

bool InputMediaSource::isLive()
{
    return m_format.m_duration <= 0;
}

bool InputMediaSource::getMetaData(const std::string& key, std::string& value)
{
    return false;
}

bool InputMediaSource::writePs(MPacket& pkt)
{
	PsParser::Packet psPacket;
	if (!m_psParser.parse(pkt.data, pkt.size, psPacket))
	{
		return false;
	}

	if (!m_format.isValid())
	{
		MediaFormat fmt = m_psParser.getFormat();
		if (fmt.isValid())
		{
			comn::AutoCritSec lock(m_cs);
			m_format = fmt;
		}
		else
		{
			return false;
		}
	}

	MPacket outPkt = MPacket();
	outPkt.data = psPacket.data;
	outPkt.size = psPacket.length;
	outPkt.flags = psPacket.flags;
	outPkt.type = psPacket.type;

	//if (psPacket.type == av::MEDIA_TYPE_VIDEO)
	{
		outPkt.pts = psPacket.ts * 1000 / 90;
	}
	
	return writePacket(outPkt);
}

bool InputMediaSource::writePacket(MPacket& pkt)
{
	if (pkt.type == av::MEDIA_TYPE_VIDEO)
	{
		//CLog::debug("InputMediaSource::writePacket. type: %d, pts: %lld\n", pkt.type, pkt.pts);

		if (m_videoPoint.isSet())
		{
			if (pkt.pts < m_videoPoint.m_pts)
			{
				CLog::warning("video pts is decreased. prev:%lld, now: %lld\n", m_videoPoint.m_pts, pkt.pts);
			}
		}

		m_videoPoint.reset(av_gettime_relative(), pkt.pts);
	}

	AVPacketPtr packet(new AVPacket(), ffmpeg::PacketDeleter());
	av_init_packet(packet.get());
	av_new_packet(packet.get(), pkt.size);
	memcpy(packet->data, pkt.data, pkt.size);

	packet->stream_index = pkt.type;
	packet->pts = pkt.pts;
	packet->dts = pkt.pts;
	packet->duration = pkt.duration;
	packet->flags = pkt.flags;

	packet->pos = av_gettime_relative();

	if (pkt.type == av::MEDIA_TYPE_SUBTITLE)
	{
		/// disable adjust delay time
		if (m_videoPoint.isSet())
		{
			TimePoint timePoint(av_gettime_relative(), pkt.pts);
			if (pkt.pts >= m_videoPoint.m_pts)
			{
				/// 字幕来的快
				if (m_delayTime > MIN_DELAY_TIME)
				{
					updateDelayTime(MIN_DELAY_TIME);
				}
			}
			else
			{
				static const int FRAME_DURATION = 40;

				int offset = ((m_videoPoint.m_pts - pkt.pts) + (timePoint.m_clock - m_videoPoint.m_clock))/1000;
				offset += FRAME_DURATION; // 额外一帧时间.

				if (m_delayTime < offset)
				{
					if (offset < MAX_DELAY_TIME)
					{
						CLog::info("adjust pkt queue delay. prev: %d, now: %d\n", m_delayTime, offset);

						updateDelayTime(offset);
					}
				}
				else if ((m_delayTime - offset) > (FRAME_DURATION*3))
				{
					updateDelayTime(m_delayTime - FRAME_DURATION);
				}
			}
		}

		m_pktQueue.insert(packet);

		comn::AutoCritSec lock(m_cs);
		m_textPacketCount++;
	}
	else
	{
		m_pktQueue.push(packet);
	}

	return true;
}

void InputMediaSource::setDelayTime(int ms)
{
	m_pktQueue.setWaterLevel(ms * 1000);

	comn::AutoCritSec lock(m_cs);
	m_delayTime = ms;
	
}

void InputMediaSource::setParams(const std::string& params)
{
	if (params.find("dump") != std::string::npos)
	{
		m_dumpEnabled = true;
	}
}

int InputMediaSource::readPacket(AVPacket& pkt)
{
	AVPacketPtr packet;
	if (!m_pktQueue.pop(packet))
	{
		return AVERROR(EAGAIN);
	}

	av_init_packet(&pkt);
	av_packet_ref(&pkt, packet.get());

	if (packet->stream_index == av::MEDIA_TYPE_SUBTITLE)
	{
		comn::AutoCritSec lock(m_cs);
		m_textPacketCount --;
	}

	return 0;
}

int InputMediaSource::readPacket(AVPacket& pkt, int ms)
{
	AVPacketPtr packet;
	if (!m_pktQueue.popEx(packet, ms, av::MEDIA_TYPE_SUBTITLE))
	{
		return AVERROR(EAGAIN);
	}

	av_init_packet(&pkt);
	av_packet_ref(&pkt, packet.get());

	if (packet->stream_index == av::MEDIA_TYPE_SUBTITLE)
	{
		comn::AutoCritSec lock(m_cs);
		m_textPacketCount--;
	}

	return 0;
}

int InputMediaSource::getTextPacketCount()
{
	comn::AutoCritSec lock(m_cs);
	return m_textPacketCount;
}

void InputMediaSource::resetTextPacketCount()
{
	comn::AutoCritSec lock(m_cs);
	m_textPacketCount = 0;
}

bool InputMediaSource::openParser(int codec)
{
	AVCodecID codecID = CodecIDConvert::toAVCodecID(codec);
	m_parser = av_parser_init(codecID);
	if (m_parser == NULL)
	{
		return false;
	}

	AVCodec* pCodec = avcodec_find_decoder(AV_CODEC_ID_H264);
	m_pContext = avcodec_alloc_context3(pCodec);

	int ret = avcodec_open2(m_pContext, pCodec, NULL);
	if (ret < 0)
	{
		avcodec_close(m_pContext);
		av_free(m_pContext);
		m_pContext = NULL;
		pCodec = NULL;

		av_parser_close(m_parser);
		m_parser = NULL;

		return false;
	}

	return isParserOpen();
}

void InputMediaSource::closeParser()
{
	if (m_pContext != NULL)
	{
		avcodec_close(m_pContext);
		av_free(m_pContext);
		m_pContext = NULL;
	}

	if (m_parser != NULL)
	{
		av_parser_close(m_parser);
		m_parser = NULL;
	}
}

bool InputMediaSource::isParserOpen()
{
	return (m_parser != NULL);
}

bool InputMediaSource::parseWrite(MPacket& pkt)
{
	if (isParserOpen())
	{
		uint8_t* buffer = pkt.data;
		int length = pkt.size;

		int pos = 0;
		int64_t vpts = pkt.pts;
		int64_t vdts = pkt.pts;

		do
		{
			uint8_t *pout = NULL;
			int poutLen = 0;
			int frameLen = av_parser_parse2(m_parser, m_pContext,
				&pout,
				&poutLen,
				buffer + pos,
				length - pos,
				vpts,
				vdts,
				pkt.pts);

			pos += frameLen;

			if (poutLen > 0)
			{
				MPacket packet = MPacket();
				packet.data = pout;
				packet.size = poutLen;
				packet.pts = vpts;
				packet.type = pkt.type;

				writePacket(packet);
			}

		} while (pos < length);

		return true;
	}
	else
	{
		return writePacket(pkt);
	}
}

void InputMediaSource::updateDelayTime(int ms)
{
	m_delayTime = ms;
	m_pktQueue.setWaterLevel(m_delayTime * 1000);
}


} /* namespace av */
